package be.rivendale.mathematics;

import be.rivendale.material.Color;

import static be.rivendale.mathematics.MathematicalUtilities.EPSILON;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

/**
 * Provides several utility methods for assertions.
 */
public class MathematicalAssert {
    /**
     * The default message to use when asserting equality of real numbers, and the client
     * did not supply a message of itself.
     */
    private static final String ASSERT_REAL_EQUALS_DEFAULT_MESSAGE = "The expected value %f is not equal to the actual value %f (using a delta of %f), but should be.";

    /**
     * Assert the equality of two real numbers.
     * If the two values are not equal using the predefined {@link MathematicalUtilities#EPSILON} then an {@link IllegalArgumentException} is thrown.
     * @param message The message to give to the thrown {@link IllegalArgumentException} when the assertion fails.
     * @param expectedValue The value that is expected to be the same as the actual value.
     * @param actualValue The actual value that should be equal to the expectedValue.
     * @throws AssertionError When the assertion fails.
     */
    public static void assertRealEquals(String message, double expectedValue, double actualValue) throws AssertionError{
        assertEquals(message, expectedValue, actualValue, EPSILON);
    }

    /**
     * Assert the equality of two real numbers, using a default message when the assertion fails.
     * @param expectedValue The value that is expected to be the same as the actual value.
     * @param actualValue The actual value that should be equal to the expectedValue.
     * @throws AssertionError When the assertion fails.
     * @see #assertRealEquals(String, double, double)
     */
    public static void assertRealEquals(double expectedValue, double actualValue) throws AssertionError {
        String defaultMessage = String.format(ASSERT_REAL_EQUALS_DEFAULT_MESSAGE, expectedValue, actualValue, EPSILON);
        assertRealEquals(defaultMessage, expectedValue, actualValue);
    }

    /**
     * Assert the equality of two {@link Point} objects.
     * This is done by comparing each of the coordinates with each other.
     * @param expected The expected point that is used to test if the actual point is indeed the same as this one.
     * @param actual The actual point that should be equal to the expected one.
     * @throws AssertionError When the assertion fails.
     */
    public static void assertPointEquals(Point expected, Point actual) throws AssertionError {
        if(!MathematicalUtilities.equals(expected.getX(), actual.getX())
                || !MathematicalUtilities.equals(expected.getY(), actual.getY())
                || !MathematicalUtilities.equals(expected.getZ(), actual.getZ())) {
            fail(Point.class.getSimpleName() + " expected " + expected + " but was " + actual);
        }
    }

    /**
     * Assert the equality of two {@link Triple} objects.
     * This is done by comparing each of the coordinates with each other.
     * @param expected The expected vector that is used to test if the actual vector is indeed the same as this one.
     * @param actual The actual vector that should be equal to the expected one.
     * @throws AssertionError When the assertion fails.
     */
    public static void assertVectorEquals(Vector expected, Vector actual) throws AssertionError {
        assertPointEquals(expected.asPoint(), actual.asPoint());
    }

        /**
     * Fails the currently running test due to an exception being expected, and not being thrown.
     */
    public static void failExceptionExpected() {
        fail("an exception is expected");
    }

    /**
     * Asserts that the specified color is actually the color that was expected,
     * by comparing the two and checking that each color component is the same.
     * @param expected The expected color, that is expected to be equal to the actual color.
     * @param actual The actual color that is under test.
     * @throws AssertionError When the assertion fails.
     * @see be.rivendale.material.Color#equals(Object)
     */
    public static void assertColorEquals(Color expected, Color actual) throws AssertionError {
        assertRealEquals(actual.getRed(), expected.getRed());
        assertRealEquals(actual.getGreen(), expected.getGreen());
        assertRealEquals(actual.getBlue(), expected.getBlue());
    }

    /**
     * Asserts that the specified rectangle is actually the rectangle that was expected,
     * by comparing the two and checking that each point is the same.
     * @param actual The actual rectangle that is under test.
     * @param expected The expected rectangle, that is expected to be equal to the actual rectangle.
     * @throws AssertionError When the assertion fails.
     */
    public static void assertRectangleEquals(Rectangle expected, Rectangle actual) throws AssertionError {
        assertPointEquals(expected.getA(), actual.getA());
        assertPointEquals(expected.getB(), actual.getB());
        assertPointEquals(expected.getC(), actual.getC());
        assertPointEquals(expected.getD(), actual.getD());
    }

    /**
     * Asserts that the specified barycentric coordinates are equal to the expected barycentric coordinates.
     * If this is not the case, an {@link AssertionError} is thrown.
     * Equal coordinates means that all u, v and w coordinates are {@link MathematicalUtilities#equals(double, double) equal}.
     * @throws AssertionError When the assertion fails.
     * @param expectedBarycentricCoordinates The barycentric coordinates that should be equal to the actual barycentric coordinates. This is the test condition value.
     * @param actualBarycentricCoordinates The barycentric coordinates under test, where they should be equal to the expected barycentric coordinates, or the test fails.
     */
//    public static void assertBaryCentricCoordinatesEquals(BarycentricCoordinates expectedBarycentricCoordinates, BarycentricCoordinates actualBarycentricCoordinates) throws AssertionError {
//        assertRealEquals(expectedBarycentricCoordinates.getU(), actualBarycentricCoordinates.getU());
//        assertRealEquals(expectedBarycentricCoordinates.getV(), actualBarycentricCoordinates.getV());
//        assertRealEquals(expectedBarycentricCoordinates.getW(), actualBarycentricCoordinates.getW());
//    }

    /**
     * Asserts that the specified actual ray is equal to the expected ray. If this is not the case, an {@link AssertionError} is thrown.
     * Equal means that both the origin and the secondary point are equal.
     * @param expectedRay The ray that is the test value to test the actual ray with. Must be equal or the assertion fails.
     * @param actualRay The ray under test, this is the ray that is to be tested to be equal to the specified expected ray.
     * @throws AssertionError When the assertion fails.
     */
    public static void assertRayEquals(Ray expectedRay, Ray actualRay) throws AssertionError {
        assertPointEquals(expectedRay.getOrigin(), actualRay.getOrigin());
        assertPointEquals(expectedRay.getPassThroughPoint(), actualRay.getPassThroughPoint());
    }

	/**
	 * Asserts that the specified triangles are equal.
	 * Two triangles are considered equal if their three points {@link Triangle#getA()} a}, {@link Triangle#getB()} b}
	 * and {@link Triangle#getC()} c} are {@link #assertPointEquals(Point, Point) asserted as equals}.
	 * @param expectedTriangle The expected triangle is the triangle to test against.
	 * @param actualTriangle The actual triangle is the triangle under test.
	 */
	public static void assertTriangleEquals(Triangle expectedTriangle, Triangle actualTriangle) {
		assertPointEquals(expectedTriangle.getA(), actualTriangle.getA());
		assertPointEquals(expectedTriangle.getB(), actualTriangle.getB());
		assertPointEquals(expectedTriangle.getC(), actualTriangle.getC());
	}
}
